﻿using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Interactivity;
using WirelessAccessPoint.DataAnnotations;

namespace WirelessAccessPoint.Interactivity
{
    /// <summary>
    /// TextBoxへの入力文字を変換、フィルタするビヘイビアです。
    /// バインドしているプロパティにStringLengthAttribute属性が付加されている場合、MaxLengthが自動的に設定されます。
    /// バインドしているプロパティにInputConverterAttribute属性が付加されている場合、入力値が変換されます。
    /// バインドしているプロパティにInputTextFilterAttribute属性が付加されている場合、入力値がフィルタされます。
    /// </summary>
    public class InputTextValidationTextBoxBehavior : Behavior<TextBox>
    {
        // .NET4.5では変数名と同じプロパティが公開されているため不要
        private static readonly PropertyInfo resolvedSource = typeof(BindingExpression).GetProperty("SourceItem", BindingFlags.Instance | BindingFlags.NonPublic);
        private static readonly PropertyInfo resolvedSourcePropertyName = typeof(BindingExpression).GetProperty("SourcePropertyName", BindingFlags.Instance | BindingFlags.NonPublic);

        private bool isActiveIme;
        private bool isActiveTextChanged;

        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.Loaded += this.Loaded;
            this.AssociatedObject.TextChanged += this.TextChanged;
            TextCompositionManager.AddPreviewTextInputStartHandler(this.AssociatedObject, this.PreviewTextInputStart);
            TextCompositionManager.AddPreviewTextInputUpdateHandler(this.AssociatedObject, this.PreviewTextInputUpdate);
            TextCompositionManager.AddPreviewTextInputHandler(this.AssociatedObject, this.PreviewTextInput);
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.Loaded -= this.Loaded;
            this.AssociatedObject.TextChanged -= this.TextChanged;
            TextCompositionManager.RemovePreviewTextInputStartHandler(this.AssociatedObject, this.PreviewTextInputStart);
            TextCompositionManager.RemovePreviewTextInputUpdateHandler(this.AssociatedObject, this.PreviewTextInputUpdate);
            TextCompositionManager.RemovePreviewTextInputHandler(this.AssociatedObject, this.PreviewTextInput);
        }

        private void Loaded(object sender, RoutedEventArgs e)
        {
            var expression = this.AssociatedObject.GetBindingExpression(TextBox.TextProperty);

            if (expression != null)
            {
                var source = resolvedSource.GetValue(expression, null);
                var propretyName = resolvedSourcePropertyName.GetValue(expression, null) as string;
                var property = TypeDescriptor.GetProperties(source)[propretyName];

                foreach (var attribute in property.Attributes)
                {
                    var length = attribute as StringLengthAttribute;

                    if (length != null)
                    {
                        this.AssociatedObject.MaxLength = length.MaximumLength;
                    }
                }
            }
        }

        private void PreviewTextInputStart(object sender, TextCompositionEventArgs e)
        {
            this.isActiveIme = true;
        }

        private void PreviewTextInputUpdate(object sender, TextCompositionEventArgs e)
        {
            if (e.TextComposition.CompositionText.Length == 0)
            {
                this.isActiveIme = false;
            }
        }

        private void PreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            this.isActiveIme = false;
        }

        private void TextChanged(object sender, TextChangedEventArgs e)
        {
            if (!this.isActiveIme && !isActiveTextChanged)
            {
                this.isActiveTextChanged = true;
                var expression = this.AssociatedObject.GetBindingExpression(TextBox.TextProperty);

                if (expression != null)
                {
                    var text = this.AssociatedObject.Text;
                    var selectionStart = this.AssociatedObject.SelectionStart;
                    var selectionLength = this.AssociatedObject.SelectionLength;

                    var source = resolvedSource.GetValue(expression, null);
                    var propretyName = resolvedSourcePropertyName.GetValue(expression, null) as string;
                    var property = TypeDescriptor.GetProperties(source)[propretyName];

                    foreach (var attribute in property.Attributes)
                    {
                        var converter = attribute as InputConverterAttribute;

                        if (converter != null)
                        {
                            text = converter.Convert(text);
                        }
                    }

                    foreach (var attribute in property.Attributes)
                    {
                        var filter = attribute as InputTextFilterAttribute;

                        if (filter != null)
                        {
                            text = new string(text.Where(c => filter.IsValid(c)).ToArray());
                        }
                    }

                    if (text != this.AssociatedObject.Text)
                    {
                        selectionStart -= this.AssociatedObject.Text.Length - text.Length;

                        if (selectionStart < 0)
                        {
                            selectionStart = 0;
                        }

                        this.AssociatedObject.Text = text;

                        if (selectionStart <= this.AssociatedObject.Text.Length)
                        {
                            this.AssociatedObject.SelectionStart = selectionStart;
                        }

                        if (selectionLength + this.AssociatedObject.SelectionStart <= this.AssociatedObject.Text.Length)
                        {
                            this.AssociatedObject.SelectionLength = selectionLength;
                        }
                    }
                }

                this.isActiveTextChanged = false;
            }
        }
    }
}
